package io.plus.interactive.collections;

import drds.common.$;
import io.plus.interactive.AsynchronousSocketChannelContext;
import io.plus.interactive.Configuration;
import io.plus.interactive.user_interface.GroupListener;
import io.plus.utils.lock.LockUtils;
import io.plus.utils.lock.ReadWriteLockMap;
import io.plus.utils.lock.ReadWriteLockSet;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;

/**
 * 多对多  (group <--> AsynchronousSocketChannelContext)<br>
 */
@Slf4j
@Data
public class Groups {

    /**
     * 对ChannelContext进行排序的比较器
     * 该对象必须在服务启动前进行设置，并且不要再去修改，否则会导致有的排序了，有的没有排序
     */
    private Comparator<AsynchronousSocketChannelContext> channelContextComparator = null;

    private ReadWriteLockMap<String, ReadWriteLockSet<AsynchronousSocketChannelContext>> groupIdToChannelContextSetMap = new ReadWriteLockMap<>(new HashMap<String, ReadWriteLockSet<AsynchronousSocketChannelContext>>());
    private String rwKey = "_io_groups_bind__";

    public static Set<AsynchronousSocketChannelContext> createSet(Comparator<AsynchronousSocketChannelContext> comparator) {
        if (comparator == null) {
            return new HashSet<AsynchronousSocketChannelContext>();
        } else {
            return new TreeSet<AsynchronousSocketChannelContext>(comparator);
        }
    }

    /**
     * 和组绑定
     */
    public void bind(String groupId, AsynchronousSocketChannelContext asynchronousSocketChannelContext) {
        bind(groupId, asynchronousSocketChannelContext, true);
    }

    /**
     * 和组绑定
     *
     * @param groupId
     * @param asynchronousSocketChannelContext
     * @param callbackListener                 是否回调GroupListener
     */
    public void bind(String groupId, AsynchronousSocketChannelContext asynchronousSocketChannelContext, boolean callbackListener) {
        if (asynchronousSocketChannelContext.clientConfiguration.isShortConnection) {
            return;
        }

        if ($.isNullOrEmpty(groupId)) {
            return;
        }

        ReadWriteLockSet<AsynchronousSocketChannelContext> channelContextSet = groupIdToChannelContextSetMap.get(groupId);
        if (channelContextSet == null) {
            try {
                LockUtils.runWriteOrWaitRead(rwKey + groupId, this, () -> {
                    if (groupIdToChannelContextSetMap.get(groupId) == null) {
                        groupIdToChannelContextSetMap.put(groupId, new ReadWriteLockSet<>(createSet(channelContextComparator)));
                    }
                });
            } catch (Exception e) {
                log.error(e.toString(), e);
            }
            channelContextSet = groupIdToChannelContextSetMap.get(groupId);
        }
        channelContextSet.add(asynchronousSocketChannelContext);
        asynchronousSocketChannelContext.getGroupIdSet().add(groupId);

        if (callbackListener) {
            GroupListener groupListener = asynchronousSocketChannelContext.clientConfiguration.getGroupListener();
            if (groupListener != null) {
                try {
                    groupListener.onAfterBind(asynchronousSocketChannelContext, groupId);
                } catch (Throwable e) {
                    log.error(e.toString(), e);
                }
            }
        }
    }

    /**
     * 一个组有哪些客户端
     */
    public ReadWriteLockSet<AsynchronousSocketChannelContext> getChannelContextSet(Configuration configuration, String groupId) {
        if (configuration.isShortConnection) {
            return null;
        }

        if ($.isNullOrEmpty(groupId)) {
            return null;
        }
        return groupIdToChannelContextSetMap.get(groupId);
    }

    public ReadWriteLockMap<String, ReadWriteLockSet<AsynchronousSocketChannelContext>> getGroupIdToChannelContextSetMap() {
        return groupIdToChannelContextSetMap;
    }

    /**
     * 某个客户端在哪些组中
     */
    public ReadWriteLockSet<String> groups(AsynchronousSocketChannelContext asynchronousSocketChannelContext) {
        Configuration configuration = asynchronousSocketChannelContext.clientConfiguration;
        if (configuration.isShortConnection) {
            return null;
        }

        return asynchronousSocketChannelContext.getGroupIdSet();
    }

    /**
     * 与所有组解除绑定
     */
    public void unbind(AsynchronousSocketChannelContext asynchronousSocketChannelContext) {
        unbind(asynchronousSocketChannelContext, true);
    }

    /**
     * @param asynchronousSocketChannelContext
     * @param callbackListener                 是否回调GroupListener
     */
    public void unbind(AsynchronousSocketChannelContext asynchronousSocketChannelContext, boolean callbackListener) {
        if (asynchronousSocketChannelContext.clientConfiguration.isShortConnection) {
            return;
        }

        try {
            ReadWriteLockSet<String> groupIdSet = asynchronousSocketChannelContext.getGroupIdSet();
            WriteLock writeLock = groupIdSet.getWriteLock();
            writeLock.lock();
            try {
                Set<String> $groupIdSet = groupIdSet.getObject();
                if ($groupIdSet != null && $groupIdSet.size() > 0) {
                    for (String groupId : $groupIdSet) {
                        try {
                            unbind(groupId, asynchronousSocketChannelContext, false, callbackListener);
                        } catch (Exception e) {
                            log.error(e.toString(), e);
                        }
                    }
                    $groupIdSet.clear();
                    asynchronousSocketChannelContext.getGroupIdSet().clear();
                }
            } catch (Exception e) {
                log.error(e.toString(), e);
            } finally {
                writeLock.unlock();
            }

        } catch (Throwable e) {
            log.error(e.toString(), e);
        }
    }

    /**
     * 与指定组解除绑定
     */
    public void unbind(String groupId, AsynchronousSocketChannelContext asynchronousSocketChannelContext) {
        unbind(groupId, asynchronousSocketChannelContext, true);
    }

    /**
     * 与指定组解除绑定
     */
    public void unbind(String groupId, AsynchronousSocketChannelContext asynchronousSocketChannelContext, boolean deleteFromChannelContext) {
        unbind(groupId, asynchronousSocketChannelContext, deleteFromChannelContext, true);
    }

    /**
     * @param groupId
     * @param asynchronousSocketChannelContext
     * @param deleteFromChannelContext
     * @param callbackListener                 是否回调GroupListener
     */
    public void unbind(String groupId, AsynchronousSocketChannelContext asynchronousSocketChannelContext, boolean deleteFromChannelContext, boolean callbackListener) {
        if (asynchronousSocketChannelContext.clientConfiguration.isShortConnection) {
            return;
        }

        if ($.isNullOrEmpty(groupId)) {
            return;
        }

        try {
            ReadWriteLockSet<AsynchronousSocketChannelContext> channelSet = groupIdToChannelContextSetMap.get(groupId);
            if (channelSet != null) {
                boolean remove = channelSet.remove(asynchronousSocketChannelContext);
                if (!remove) {
                    log.warn("{}, 移除失败,group:{} cid:{}", asynchronousSocketChannelContext, groupId, asynchronousSocketChannelContext.getId());
                }

                if (deleteFromChannelContext) {
                    asynchronousSocketChannelContext.getGroupIdSet().remove(groupId);
                }

                if (callbackListener) {
                    GroupListener groupListener = asynchronousSocketChannelContext.clientConfiguration.getGroupListener();
                    if (groupListener != null) {
                        try {
                            groupListener.onAfterUnbind(asynchronousSocketChannelContext, groupId);
                        } catch (Throwable e) {
                            log.error(e.toString(), e);
                        }
                    }
                }

                //如果该群组没有任何连接，就把这个群组从map中删除，以释放空间
                if (channelSet.getObject().size() == 0) {
                    groupIdToChannelContextSetMap.remove(groupId);
                }
            }
        } catch (Exception e) {
            log.error(e.toString(), e);
        }
    }
}
